/** 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package it.free.i.virtual.machine.hal.devicealias.laboratory.simulator.dynamicparameter.impl;

import it.free.i.management.JmxFacilities;
import it.free.i.management.JmxRegisterServer;
import it.free.i.virtual.machine.hal.DeviceAliasRuntimeException;
import it.free.i.virtual.machine.hal.LinkingOperationException;
import it.free.i.virtual.machine.hal.devicealias.command.DeviceAliasPortCommandVerb;
import it.free.i.virtual.machine.hal.devicealias.laboratory.simulator.dynamicparameter.DynamicParameter;
import it.free.i.virtual.machine.hal.devicealias.laboratory.simulator.dynamicparameter.DynamicParameterAlgorithms;
import it.free.i.virtual.machine.hal.devicealias.laboratory.simulator.dynamicparameter.Levels;
import it.free.i.virtual.machine.hal.devicealias.port.DeviceAliasPortSupportImpl;
import it.free.i.virtual.machine.hal.devicealias.echo.DynamicParameterChangingEcho;
import it.free.i.virtual.machine.hal.devicealias.echo.DynamicParameterChangingEchoImpl;

import it.free.i.virtual.machine.hal.devicealias.controller.DeviceAliasPortMessageListener;
import it.free.i.virtual.machine.hal.devicealias.port.DeviceAliasPort;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Observable;
import java.util.Properties;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.Notification;
import javax.management.ReflectionException;

import org.apache.log4j.Logger;

/**  
 * Decreasing value up to reaching the threshold set. Decrease by one per loop.
 * @author stefano
 */
public class DynamicParameterDecreasingImpl extends DeviceAliasPortSupportImpl implements DeviceAliasPort, DynamicParameter {

	private String myParamterName = null;
	private long myNumberOfBeats = 0;
	private long myBeatDuration = 0L;
	private long myThreshold = 0L;
	private int myNumberOfHeartBeatsDone = 0;
	private Levels myLevel = null;
	private long[] myLevelValues = null;
	private int numberOfLevels = 0;
	private String myReactOnCollisionWithObject = null; 
	private long sequenceNumber = 0;
	
	private static Logger logger = Logger.getLogger(DynamicParameterDecreasingImpl.class);
	
	/**
	 * Class builder, set the observer to notify for level changes
	 */
	public DynamicParameterDecreasingImpl(){}
	
	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.laboratory.simulator.dynamicparameter.DynamicParameter#getThreshold()
	 */
	public long getThreshold() {
		return this.myThreshold;
	}

	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.laboratory.simulator.dynamicparameter.DynamicParameter#setHeartBeat(long, long)
	 */
	public void setHeartBeat(long numberOfBeats, long beatDuration) {
		this.myNumberOfBeats = numberOfBeats;
		this.myBeatDuration = beatDuration;
		this.myThreshold = this.myNumberOfBeats;
		logger.info("[" + this.myParamterName + "] threshold set to beats number: [" + this.myNumberOfBeats + "]");
	}

	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.laboratory.simulator.dynamicparameter.DynamicParameter#setName(String)
	 */
	public void setName(String parameterName) {
		this.myParamterName = parameterName;
	}
	
	/* *
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public void run() {
		
		logger.debug("[" + this.myParamterName + "] is starting to heartbeat with fluctuation algorithm: [" + DynamicParameterAlgorithms.DECREASING.toString() + "]");
		
		JmxRegisterServer.getInstance().registerBean(this);
		addNotificationListener(DeviceAliasPortMessageListener.getInstance(), null, null);
		
		if( (this.myParamterName == null) || (this.myParamterName.trim().equals("")) ){
			 this.myParamterName = "PARAMETER NAME UNSET ! Signaling for this parameter will be useless";
		}
		
		//Auto compute the levels
		computeLevels();
		
		for(; this.myNumberOfHeartBeatsDone < this.myNumberOfBeats; this.myNumberOfHeartBeatsDone++){
			long tmpCurrentLevelValue = 0L;
					
			for(int i = 0; i < this.numberOfLevels; i++){
				tmpCurrentLevelValue = this.myLevelValues[i];
				if( this.myNumberOfHeartBeatsDone < tmpCurrentLevelValue){
					this.myLevel = Levels.values()[i];
					this.notifyMyObservers(this.myLevel);
					//logger.debug("Heart beat " + this.myNumberOfHeartBeatsDone + ", current " + this.myParamterName + " level: [" + this.myLevel.toString() + "]");
					break;
				}	
			}
			
			if(this.myNumberOfHeartBeatsDone == (this.myNumberOfBeats - 1)){
			   this.myNumberOfHeartBeatsDone --;
			}
			
			try{
				Thread.sleep(this.myBeatDuration);
			}catch(InterruptedException iExc){
				   logger.error("Unable to heartbeat [" + this.myParamterName + "]", iExc);
			}
		}
	}

	/**
	 * Do all the calls to notify obsevers about parameter floating events
	 * @param level
	 */
	private void notifyMyObservers(Levels level){
	
		DynamicParameterChangingEcho dynamicParameterChangingEcho = new DynamicParameterChangingEchoImpl();
		
		dynamicParameterChangingEcho.setDynamicParameterName(this.myParamterName);
		dynamicParameterChangingEcho.setLevel(this.myLevel);
		
		Notification notification = super.buildNotification(DeviceAliasPortCommandVerb.UNSOLICITED_VALUE_CHANGED, 
		        											this.getName(), 
		        											sequenceNumber++,
		        											dynamicParameterChangingEcho);
		
		super.sendNotification(notification);
	}
	
	/**
	 * This update is called whenever this value get a refill action: this is like to put gasoline in 
	 * the car, the fuel indicator shift up
	 */
	public void update(Observable observable, Object update) {
		//Doesn't matter signaling, just decrease by one the number of hearbeats already done, to switch the current level
		//logger.info("Parameter reload one unit update received, source is: [ " + observable + "]");
		this.myNumberOfHeartBeatsDone--;
		
		if(this.myNumberOfHeartBeatsDone < 0){
		   this.myNumberOfHeartBeatsDone = 0;
		}
	}
	
	/**
	 * Compute and set the values for the colors' threshold
	 */
	private void computeLevels(){
		//Extract the number of levels by the levels enum
		this.numberOfLevels = Levels.values().length;
		this.myLevelValues = new long[this.numberOfLevels];
		long oneLevelValue = this.myThreshold / numberOfLevels;
		
		for(int i=0; i < this.numberOfLevels; i++){
			this.myLevelValues[i] = (oneLevelValue * (i + 1));
			logger.info("Level [" + i + "] threshold computed: [" + this.myLevelValues[i] + "]");
		}
	}

	@Override
	public String toString(){
	
		String mySignature = this.getClass().getSimpleName() + " " + this.myParamterName + " " + this.myLevel.toString();
		return mySignature;
	}

	/**
	 * @return the myReactOnCollisionWithObject
	 */
	public String getReactOnCollisionWithObject() {
		return this.myReactOnCollisionWithObject;
	}

	/**
	 * @param myReactOnCollisionWithObject the myReactOnCollisionWithObject to set
	 */
	public void setReactOnCollisionWithObject(String myReactOnCollisionWithObject) {
		this.myReactOnCollisionWithObject = myReactOnCollisionWithObject;
	}

	@Override
	public Object getAttribute(String attribute)
			throws AttributeNotFoundException, MBeanException,
			ReflectionException {
		return null;
	}

	@Override
	public AttributeList getAttributes(String[] attributes) {
		return null;
	}

	@Override
	public MBeanInfo getMBeanInfo() {
		return JmxFacilities.buildMBeanInfo(this.getClass(), this.getName());
	}

	@Override
	public DeviceAliasPortCommandVerb getMode() {
		return null;
	}

	@Override
	public String getName() {
		return this.myParamterName;
	}

	@Override
	public void handleNotification(Notification notification, Object handback) {
	}

	@Override
	public Object invoke(List<Object> input) {
		return null;
	}

	@Override
	public Object invoke(String actionName, 
						 Object[] params, 
						 String[] signature) throws MBeanException, ReflectionException {
			
		Object invocationReturnValue = null;
	    
		try{
            Method method = this.getClass().getMethod(actionName, new Class[params.length]);
            invocationReturnValue = method.invoke(this, params);
            logger.info("Invoked action [" + actionName + "] invoked method [" + method.getName() + "]");
        }catch(NoSuchMethodException nsmExc) {    
               throw (new MBeanException(nsmExc));
        }catch(SecurityException sExc) {
               throw (new MBeanException(sExc));
        }catch(IllegalArgumentException iaExc) {
               throw (new MBeanException(iaExc));
        }catch(IllegalAccessException iaExc) {
               throw (new MBeanException(iaExc));    
        }catch(InvocationTargetException itExc) {
               throw (new MBeanException(itExc));
        }
        
        return invocationReturnValue;
	}

	public void link(Properties virtualDeviceConfiguration)
			throws LinkingOperationException {
	}

	public void setAttribute(Attribute attribute)
			throws AttributeNotFoundException, InvalidAttributeValueException,
			MBeanException, ReflectionException {
	}

	public AttributeList setAttributes(AttributeList attributes) {
		return null;
	}

	public void setMode(DeviceAliasPortCommandVerb mode) {
	}
	
	public void unlink() throws LinkingOperationException {
	}

	public void reset() throws DeviceAliasRuntimeException {
	}
	
}
